﻿/*
 *  Spell.cs
 *  This file is mostly taken from PureRotation with minor edits, so credits to the PureRotation team.
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using CommonBehaviors.Actions;
using Portal.Managers;
using Styx;
using Styx.CommonBot;
using Styx.Helpers;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Portal.Helpers;
using Action = Styx.TreeSharp.Action;
using Lua = Styx.WoWInternals.Lua;

namespace Portal.Core
{

    internal static class Spell
    {      

        public delegate WoWUnit UnitSelectionDelegate(object context);
        private static LocalPlayer Me { get { return StyxWoW.Me; } }
        //public static double GCD { get { return 1; } }  //SpellManager.GlobalCooldownLeft.TotalSeconds;
        public static bool IsChanneling { get { return StyxWoW.Me.IsChanneling && StyxWoW.Me.ChanneledCastingSpellId != 0; } }
        private static bool CheckMoving
        { 
            get 
            { 
                if (Me.Class == WoWClass.Warlock && TalentManager.HasTalent(17)) return false;
                if (Me.Class == WoWClass.Mage && Me.HasAura("Ice Floes")) return false;
                return true;
            } 
        }

        public static string Lastspellcast;
        public static readonly HashSet<int> HeroismBuff = new HashSet<int>
        {
            32182, //Bloodlust - Shaman (Horde)
            2825, // Heroism - Shaman (Alliance)
            80353, // Time Warp - Mages
            90355 // Ancient Hysteria - Core Hound - Hunter Pet
        };

        public static Composite CreateWaitForLagDuration()
        {
            return new WaitContinue(TimeSpan.FromMilliseconds((StyxWoW.WoWClient.Latency * 2) + 150), ret => false, new ActionAlwaysSucceed());
        }

        #region GCD

        public static bool GlobalCooldown(bool allowLagTollerance = true)
        {
            var latency = allowLagTollerance ? StyxWoW.WoWClient.Latency << 1 : 0;
            var gcdTimeLeft = GlobalCooldownLeft.TotalMilliseconds;

            using (StyxWoW.Memory.AcquireFrame())
                return gcdTimeLeft > latency;
        }

        private static TimeSpan GlobalCooldownLeft
        {
            get
            {
                try
                {
                    if (GetGlobalCooldownSpell != null)
                        return GetGlobalCooldownSpell.CooldownTimeLeft;
                }
                catch (AccessViolationException)
                {
                    return SpellManager.GlobalCooldownLeft;
                }
                catch (InvalidObjectPointerException)
                {
                    return SpellManager.GlobalCooldownLeft;
                }

                return SpellManager.GlobalCooldownLeft;
            }
        }

        private static readonly HashSet<int> GCDSPELLS = new HashSet<int>
            {
                83968, // Mass Resurrection
                SpellId.ShadowBolt,
                SpellId.IceLance
            };

        private static WoWSpell GetGlobalCooldownSpell
        {
            get
            {
                return SpellManager.Spells.FirstOrDefault(s => GCDSPELLS.Contains(s.Value.Id)).Value;
            }
        }

        #endregion GCD

        #region Double Cast

        public static readonly Dictionary<string, DoubleCastSpell> DoubleCastEntries = new Dictionary<string, DoubleCastSpell>();
        
        private static void UpdateDoubleCastEntries(string spellName, double expiryTime)
        {
            if (DoubleCastEntries.ContainsKey(spellName)) DoubleCastEntries[spellName] = new DoubleCastSpell(spellName, expiryTime, DateTime.UtcNow);
            if (!DoubleCastEntries.ContainsKey(spellName)) DoubleCastEntries.Add(spellName, new DoubleCastSpell(spellName, expiryTime, DateTime.UtcNow));
        }

        public static void OutputDoubleCastEntries()
        {
            foreach (var spell in DoubleCastEntries)
            {
                Log.Info(spell.Key + " time: " + spell.Value.DoubleCastCurrentTime);
            }
        }

        internal static void PulseDoubleCastEntries()
        {
            DoubleCastEntries.RemoveAll(t => DateTime.UtcNow.Subtract(t.DoubleCastCurrentTime).TotalSeconds >= t.DoubleCastExpiryTime);
        }

        public static Composite PreventDoubleCast(string spell, double expiryTime, Selection<bool> reqs = null)
        {
            return PreventDoubleCast(spell, expiryTime, ret => StyxWoW.Me.CurrentTarget, reqs);
        }

        public static Composite PreventDoubleCast(string spell, double expiryTime, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) && onUnit != null && onUnit(ret) != null
                            && !DoubleCastEntries.ContainsKey(spell + onUnit(ret).Guid)
                            && !onUnit(ret).HasDoNotDamageAura()
                            && SpellManager.CanCast(spell, onUnit(ret), true, CheckMoving),
                    new Sequence(
                        new Action(a => SpellManager.Cast(spell, onUnit(a))),
                        new Action(a => UpdateDoubleCastEntries(spell + onUnit(a).Guid, expiryTime)),
                        new Action(ret => Lastspellcast = spell),
                        new Action(ret => Log.Info("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName ?? "Unknown"))
                        ));
        }

        public static Composite PreventDoubleCastIgnoreMoving(string spell, double expiryTime, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) && onUnit != null && onUnit(ret) != null
                            && !DoubleCastEntries.ContainsKey(spell + onUnit(ret).Guid)
                            && !onUnit(ret).HasDoNotDamageAura()
                            && SpellManager.CanCast(spell, onUnit(ret), true, false),
                    new Sequence(
                        new Action(a => SpellManager.Cast(spell, onUnit(a))),
                        new Action(a => UpdateDoubleCastEntries(spell + onUnit(a).Guid, expiryTime)),
                        new Action(ret => Lastspellcast = spell),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName)))
                        ));
        }

        public static Composite GlobalPreventDoubleCast(string spell, double expiryTime, UnitSelectionDelegate onUnit, Selection<bool> reqs = null, bool ignoreMoving = false, string reason = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) && onUnit != null && onUnit(ret) != null
                            && !DoubleCastEntries.ContainsKey(spell)
                            && !onUnit(ret).HasDoNotDamageAura()
                            && SpellManager.CanCast(spell, onUnit(ret), true, CheckMoving),
                    new Sequence(
                        new Action(a => SpellManager.Cast(spell, onUnit(a))),
                        new Action(a => UpdateDoubleCastEntries(spell, expiryTime)),
                        new Action(ret => Lastspellcast = spell),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName)))
                        ));
        }

        public static Composite PreventDoubleCast(int spell, double expiryTime, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) && onUnit != null && onUnit(ret) != null
                           && !DoubleCastEntries.ContainsKey(spell.ToString(CultureInfo.InvariantCulture) + onUnit(ret).Guid)
                           && !onUnit(ret).HasDoNotDamageAura()
                           && SpellManager.CanCast(spell, onUnit(ret), true),
                    new Sequence(
                        new Action(a => SpellManager.Cast(spell, onUnit(a))),
                        new Action(a => UpdateDoubleCastEntries(spell.ToString(CultureInfo.InvariantCulture) + onUnit(a).Guid, expiryTime))));
        }

        public static Composite PreventDoubleCastNoCanCast(string spell, double expiryTime, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                        && onUnit != null && onUnit(ret) != null 
                        && !DoubleCastEntries.ContainsKey(spell + onUnit(ret).Guid)
                        && !onUnit(ret).HasDoNotDamageAura(),
                    new Sequence(
                        new Action(a => SpellManager.Cast(spell, onUnit(a))),
                        new Action(ret => Lastspellcast = spell),
                        new Action(a => UpdateDoubleCastEntries(spell + onUnit(a).Guid, expiryTime))));
        }

        public static Composite PreventDoubleCast(int spell, double expiryTime, Selection<bool> reqs = null )
        {
            return PreventDoubleCast(spell, expiryTime, target => Me.CurrentTarget, reqs);
        }

        public static Composite PreventDoubleChannel(string spell, double expiryTime, bool checkCancast, Selection<bool> reqs = null)
        {
            return PreventDoubleChannel(spell, expiryTime, checkCancast, onUnit => StyxWoW.Me.CurrentTarget, reqs);
        }

        public static Composite PreventDoubleChannel(string spell, double expiryTime, bool checkCancast, UnitSelectionDelegate onUnit, Selection<bool> reqs)
        {
            return new Decorator(
                delegate(object a)
                    {
                        if (IsChanneling)
                            return false;
                    
                        if (!reqs(a))
                            return false;

                        if (onUnit != null && DoubleCastEntries.ContainsKey(spell + onUnit(a).Guid) && !onUnit(a).HasDoNotDamageAura())
                            return false;

                        if (onUnit != null && (checkCancast && !SpellManager.CanCast(spell, onUnit(a), true, CheckMoving)))
                            return false;

                        return true;
                    },
                new Sequence(
                    new Action(a => SpellManager.Cast(spell, onUnit(a))),
                    new Action(ret => Lastspellcast = spell),
                    new Action(a => UpdateDoubleCastEntries(spell + onUnit(a).Guid, expiryTime)),
                    new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName)))
                    ));
        }


        public static Composite PreventDoubleCastOnGround(string spell, double expiryTime, LocationRetriever onLocation)
        {
            return PreventDoubleCastOnGround(spell, expiryTime, onLocation, ret => true);
        }

        public static Composite PreventDoubleCastOnGround(string spell, double expiryTime, LocationRetriever onLocation, CanRunDecoratorDelegate requirements, bool waitForSpell = false)
        {
            return new Decorator(
                    ret =>
                    onLocation != null && requirements(ret) && SpellManager.CanCast(spell, true) &&
                    !BossList.IgnoreAoE.Contains(StyxWoW.Me.CurrentTarget.Entry) &&
                    (StyxWoW.Me.Location.Distance(onLocation(ret)) <= SpellManager.Spells[spell].MaxRange ||
                     SpellManager.Spells[spell].MaxRange == 0) && !DoubleCastEntries.ContainsKey(spell + onLocation(ret)), 
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spell)),

                        new DecoratorContinue(ctx => waitForSpell,
                            new WaitContinue(1,
                                ret =>
                                StyxWoW.Me.CurrentPendingCursorSpell != null &&
                                StyxWoW.Me.CurrentPendingCursorSpell.Name == spell, new ActionAlwaysSucceed())),

                        new Action(ret => SpellManager.ClickRemoteLocation(onLocation(ret))),
                        new Action(ret => UpdateDoubleCastEntries(spell + onLocation(ret), expiryTime))));
        }

        public static Composite GlobalPreventDoubleCastOnGround(string spell, double expiryTime, LocationRetriever onLocation, CanRunDecoratorDelegate requirements)
        {
            return new Decorator(
                    ret =>
                    onLocation != null && requirements(ret) && SpellManager.CanCast(spell, true) 
                    && !DoubleCastEntries.ContainsKey(spell),
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spell)),
                        new Action(ret => SpellManager.ClickRemoteLocation(onLocation(ret))),
                        new Action(ret => Lastspellcast = spell),
                        new Action(ret => UpdateDoubleCastEntries(spell, expiryTime))));
        }

        public struct DoubleCastSpell
        {
            public DoubleCastSpell(string spellName, double expiryTime, DateTime currentTime)
                : this()
            {

                DoubleCastSpellName = spellName;
                DoubleCastExpiryTime = expiryTime;
                DoubleCastCurrentTime = currentTime;
            }

            public string DoubleCastSpellName { get; set; }
            public double DoubleCastExpiryTime { get; set; }
            public DateTime DoubleCastCurrentTime { get; set; }
        }

        #endregion

        #region Cast - by name 
        public static Composite Cast(string spell, Selection<bool> reqs = null, string reason = null)
        {
            return Cast(spell, ret => StyxWoW.Me.CurrentTarget, reqs);
        }

        public static Composite Cast(string spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => (onUnit != null &&
                        onUnit(ret) != null &&
                        (reqs == null || reqs(ret)) &&
                        !onUnit(ret).HasDoNotDamageAura() &&
                        SpellManager.CanCast(spell, onUnit(ret), true, CheckMoving)),
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spell, onUnit(ret))),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName))),
                        new Action(ret => Lastspellcast = spell))
                    );
        }

        public static Composite CastIgnoreMoving(string spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => (onUnit != null &&
                        onUnit(ret) != null &&
                        (reqs == null || reqs(ret)) &&
                        !onUnit(ret).HasDoNotDamageAura() &&
                        SpellManager.CanCast(spell, onUnit(ret), true, false)),
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spell, onUnit(ret))),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", spell, onUnit(ret).SafeName))),
                        new Action(ret => Lastspellcast = spell))
                    );
        }

        /// <summary>
        /// used for warlocks and shit like this Spell.CastHack("Metamorphosis: Doom", "Doom", on => Me.CurrentTarget, ret =>  NeedDoom));
        /// </summary>
        /// <param name="canCastName">1st is the spell name SpellManager expects</param>
        /// <param name="castName">2nd is the spell the game expects</param>
        /// <returns></returns>
        public static Composite CastHack(string canCastName, string castName, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(ret => castName != null && canCastName != null 
                    && ((reqs != null && reqs(ret)) || (reqs == null)) 
                    && onUnit != null && onUnit(ret) != null
                    && !onUnit(ret).HasDoNotDamageAura()
                    && SpellManager.CanCast(canCastName, onUnit(ret), true, CheckMoving),
                new Sequence(
                    new Action(ret => SpellManager.Cast(castName, onUnit(ret))),
                    new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", castName, onUnit(ret).SafeName))),
                    new Action(ret => Lastspellcast = castName)));
        }

        #endregion

        #region Cast - by ID

        public static Composite Cast(int spell, Selection<bool> reqs = null)
        {
            return Cast(spell, ret => StyxWoW.Me.CurrentTarget, reqs);
        }

        public static Composite Cast(int spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                        && onUnit != null && onUnit(ret) != null
                        && !onUnit(ret).HasDoNotDamageAura()
                        && SpellManager.CanCast(spell, onUnit(ret), true),
                    new Action(ret => SpellManager.Cast(spell, onUnit(ret))));
        }

        /// <summary>
        /// Cast Spell Without Checking CanCast
        /// </summary>
        /// <param name="spell"></param>
        /// <param name="onUnit"></param>
        /// <param name="reqs"></param>
        /// <returns></returns>
        public static Composite ForceCast(int spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                           && onUnit != null && onUnit(ret) != null 
                           && !WoWSpell.FromId(spell).Cooldown 
                           && onUnit(ret).Distance < 40
                           && !onUnit(ret).HasDoNotDamageAura()
                           && onUnit(ret).InLineOfSpellSight,
                           new Sequence(
                               new Action(ret => SpellManager.Cast(spell, onUnit(ret))),
                               new Action(ret => Log.Info(String.Format("[Casting: {0}] [On: {1}]", WoWSpell.FromId(spell).Name, onUnit(ret).SafeName)))
                            ));
        }

        public static Composite ForceCastOnGround(int spellid, UnitSelectionDelegate onUnit, Selection<bool> reqs = null, bool waitForSpell = false)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                           && onUnit != null && onUnit(ret) != null
                           && !WoWSpell.FromId(spellid).Cooldown 
                           && onUnit(ret).Distance < 40
                           && !onUnit(ret).HasDoNotDamageAura()
                           && onUnit(ret).InLineOfSpellSight,
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spellid)),

                        new DecoratorContinue(ctx => waitForSpell,
                            new WaitContinue(1,
                                ret =>
                                StyxWoW.Me.CurrentPendingCursorSpell != null &&
                                StyxWoW.Me.CurrentPendingCursorSpell.Id == spellid, new ActionAlwaysSucceed())),

                        new Action(ret => SpellManager.ClickRemoteLocation(onUnit(ret).Location)),
                        new Action(ret => Log.Info(String.Format("[Casting at Location: {0}] [On: {1}]", WoWSpell.FromId(spellid).Name, onUnit(ret).SafeName)))
                        ));
        }

        #endregion

        #region Cast - by Specific Requirements and Functionality

        /// <summary>
        ///  Casts a raid buff if the buff is not currently applied on the player or the raid
        /// </summary>
        /// <param name="name">name of the buff to cast</param>
        /// <param name="cond">The conditions that must be true</param>
        /// <returns>true or false</returns>
        public static Composite CastRaidBuff(string name, CanRunDecoratorDelegate cond)
        {
            return new Decorator(
                delegate(object a)
                    {

                        if (!cond(a))
                            return false;

                        if (!SpellManager.CanCast(name, Me, true, CheckMoving))
                        {
                            return false;
                        }

                        // If we are solo then return true if the name of the requested buff matchs the users UI setting and the player does not have the buff..
                        //if (!Unit.IsInGroup && !Me.IsDead && !Me.IsGhost && Me.IsAlive)
                        //{
                        //    if (name.Contains(PRSettings.Instance.Warrior.ShoutSelection.ToString()) && !PlayerHasBuff(name)) return true;
                        //    if (name.Contains(PRSettings.Instance.Monk.LegacySelection.ToString()) && !PlayerHasBuff(name)) return true;
                        //    if (name.Contains(PRSettings.Instance.Paladin.BlessingSelection.ToString()) && !PlayerHasBuff(name)) return true;
                        //}

                        // Continue on if we are in a raid group and check all raid members for the buffs we can provide and cast them if ok.
                        var players = new List<WoWPlayer> { Me };
                        if (Me.GroupInfo.IsInRaid)
                        {
                            players.Remove(Me);
                            players.AddRange(Me.RaidMembers);
                        }
                        else if (Me.GroupInfo.IsInParty)
                        {
                            players.AddRange(Me.PartyMembers);
                        }

                        var ProvidablePlayerBuffs = new HashSet<int>();
                        switch (StyxWoW.Me.Class)
                        {
                            case WoWClass.Warrior:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stamina);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackPower);

                                break;
                            case WoWClass.Paladin:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stats);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Mastery);

                                break;
                            case WoWClass.Hunter:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackPower);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.CriticalStrike);

                                break;
                            case WoWClass.Rogue:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackSpeed);

                                break;
                            case WoWClass.Priest:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stamina);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellHaste);

                                break;
                            case WoWClass.DeathKnight:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackPower);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackSpeed);

                                break;
                            case WoWClass.Shaman:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.AttackSpeed);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellPower);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellHaste);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Mastery);

                                break;
                            case WoWClass.Mage:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellPower);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.CriticalStrike);

                                break;
                            case WoWClass.Warlock:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stamina);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellPower);

                                break;
                            case WoWClass.Druid:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stats);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.CriticalStrike);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.SpellHaste);
                                break;

                            case WoWClass.Monk:
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Stats);
                                ProvidablePlayerBuffs.UnionWith(SpellLists.Mastery);
                                break;
                        }

                        return players.Any(x => x.Distance2DSqr < 40 * 40 && !x.HasAnyAura(ProvidablePlayerBuffs) && !x.IsDead && !x.IsGhost && x.IsAlive);
                    },
                new Sequence(
                    new Action(a => SpellManager.Cast(name))));
        }

        #endregion

        #region Cast - Multi DoT

        /// <summary> Multi-DoT targets within range of target</summary>
        public static Composite MultiDoT(string spellName, WoWUnit unit, Selection<bool> reqs = null)
        {
            return MultiDoT(spellName, unit, 15, 1, reqs);
        }

        /// <summary> Multi-DoT targets within range of target</summary>
        public static Composite MultiDoT(string spellName, WoWUnit unit, double radius, int refreshDurationRemaining, Selection<bool> reqs = null)
        {
            WoWUnit dotTarget = null;
            return new Decorator(ret => unit != null && ((reqs != null && reqs(ret)) || reqs == null),
                                 new PrioritySelector(
                                     ctx =>
                                     dotTarget =
                                     Unit.GetMultiDoTTarget(unit, spellName, radius, refreshDurationRemaining),
                                     PreventDoubleCast(spellName, GetSpellCastTime(spellName) + 0.5, on => dotTarget, ret => dotTarget != null)));
        }

        /// <summary> Multi-DoT targets within range of target</summary>
        public static Composite PreventDoubleMultiDoT(string spellName, double expiryTime, WoWUnit unit, double radius, double refreshDurationRemaining, Selection<bool> reqs = null)
        {
            WoWUnit dotTarget = null;
            return new Decorator(ret => unit != null && ((reqs != null && reqs(ret)) || reqs == null),
                              new PrioritySelector(ctx => dotTarget = Unit.GetMultiDoTTarget(unit, spellName, radius, refreshDurationRemaining),
                                  PreventDoubleCast(spellName, expiryTime, on => dotTarget, ret => dotTarget != null)));
        }

        #endregion Cast - Multi DoT

        #region Cast - Pet Action

        private static bool CanCastPetAction(string action)
        {
            WoWPetSpell petAction = StyxWoW.Me.PetSpells.FirstOrDefault(p => p.ToString() == action);
            if (petAction == null || petAction.Spell == null)
            {
                //Log.Debug("Can't Cast Pet Cast: {0}", action);
                return false;
            }

            //Log.Debug("Can Cast Pet Cast: {0}", action);
            return !petAction.Spell.Cooldown;
        }

        public static void CastPetAction(string spellName, WoWUnit unit)
        {
            WoWPetSpell spell = StyxWoW.Me.PetSpells.FirstOrDefault(p => p.ToString() == spellName);
            if (spell == null)
                return;

            if (!CanCastPetAction(spellName)) return;

            Lua.DoString("CastPetAction({0}, {1})", spell.ActionBarIndex + 1, unit.Name);

            Log.Info("[Pet] [Casting: {0}] [On: {1}]", spellName, unit.SafeName);
        }

        public static void CastPetAction(string spellName)
        {
            WoWPetSpell spell = StyxWoW.Me.PetSpells.FirstOrDefault(p => p.ToString() == spellName);
            if (spell == null)
                return;

            if (!CanCastPetAction(spellName)) return;

            Lua.DoString("CastPetAction({0})", spell.ActionBarIndex + 1);

            Log.Info("[Pet] [Casting: {0}]", spellName);
        }

        public static Composite CastPetAction(string spellName, UnitSelectionDelegate onUnit, CanRunDecoratorDelegate conditions)
        {
            return new Decorator(
                ret => conditions(ret) && onUnit != null && onUnit(ret) != null,
                new Sequence(
                    new Action(ret => CastPetAction(spellName, onUnit(ret)))
                    ));
        }

        public static Composite CastPetActionOnLocation(string spellName, LocationRetriever location, CanRunDecoratorDelegate conditions)
        {
            return new Decorator(
                ret => conditions(ret) && CanCastPetAction(spellName),
                new Sequence(
                    new Action(ret => CastPetAction(spellName)),
                    new WaitContinue(TimeSpan.FromMilliseconds(250), ret => false, new ActionAlwaysSucceed()),
                    new Action(ret => SpellManager.ClickRemoteLocation(location(ret))),
                    new Action(ret => Log.Info("[Pet] Cast at Location: {0}", spellName))
                    ));
        }

        public static Composite PreventDoubleCastPetActionOnLocation(string spellName, double expiryTime, LocationRetriever location, CanRunDecoratorDelegate conditions)
        {
            return new Decorator(ret =>
                        ((conditions != null && conditions(ret)) || (conditions == null))
                        && CanCastPetAction(spellName)
                        && !DoubleCastEntries.ContainsKey(spellName.ToString(CultureInfo.InvariantCulture) + Me.GetHashCode()),
                       new Sequence(
                        new Action(a => UpdateDoubleCastEntries(spellName.ToString(CultureInfo.InvariantCulture) + Me.GetHashCode(), expiryTime)),
                        new Action(ret => CastPetAction(spellName)),
                        new WaitContinue(TimeSpan.FromMilliseconds(250), ret => false, new ActionAlwaysSucceed()),
                        new Action(ret => SpellManager.ClickRemoteLocation(location(ret))),
                        new Action(ret => Log.Info("[Pet] [Cast at Location: {0}]", location(ret)))
                           )
                           );
        }

        #endregion

        #region ChanneledSpell - by name

        public static Composite ChannelSpell(string spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                        && onUnit != null && onUnit(ret) != null
                        && !onUnit(ret).HasDoNotDamageAura()
                        && SpellManager.CanCast(spell, onUnit(ret), true, CheckMoving) 
                        && !IsChanneling,
                    new Action(ret => SpellManager.Cast(spell, onUnit(ret))));
        }

        #endregion

        #region ChanneledSpell - by ID

        public static Composite ChannelSpell(int spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return
                new Decorator(
                    ret => ((reqs != null && reqs(ret)) || (reqs == null)) 
                        && onUnit != null && onUnit(ret) != null
                        && !onUnit(ret).HasDoNotDamageAura()
                        && SpellManager.CanCast(spell, onUnit(ret), true) && !IsChanneling,
                    new Action(ret => SpellManager.Cast(spell, onUnit(ret))));
        }

        #endregion

        #region CastOnGround - placeable spell casting

        #region Delegates

        public delegate WoWPoint LocationRetriever(object context);

        #endregion

        /// <summary>
        ///   Creates a behavior to cast a spell by name, on the ground at the specified location.
        ///   Returns RunStatus.Success if successful, RunStatus.Failure otherwise.
        /// </summary>
        public static Composite CastOnGround(string spell, LocationRetriever onLocation)
        {
            return CastOnGround(spell, onLocation, ret => true);
        }

        public static Composite CastOnGround(string spell, LocationRetriever onLocation, CanRunDecoratorDelegate requirements, bool waitForSpell = false)
        {
            return
                new Decorator(
                    ret =>
                    onLocation != null && requirements(ret) && SpellManager.CanCast(spell, true) &&
                    !BossList.IgnoreAoE.Contains(StyxWoW.Me.CurrentTarget.Entry) &&
                    (StyxWoW.Me.Location.Distance(onLocation(ret)) <= SpellManager.Spells[spell].MaxRange ||
                     SpellManager.Spells[spell].MaxRange == 0),  //&& PRSettings.Instance.UseAoEAbilities  && GameWorld.IsInLineOfSpellSight(StyxWoW.Me.Location, onLocation(ret))
                    new Sequence(
                       new Action(ret => SpellManager.Cast(spell)),

                        new DecoratorContinue(ctx => waitForSpell,
                            new WaitContinue(1,
                                ret =>
                                StyxWoW.Me.CurrentPendingCursorSpell != null &&
                                StyxWoW.Me.CurrentPendingCursorSpell.Name == spell, new ActionAlwaysSucceed())),

                        new Action(ret => SpellManager.ClickRemoteLocation(onLocation(ret))),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [Loc: {1}]", spell, onLocation(ret))))
                        ));
        }


        /// <summary>
        ///   Creates a behavior to cast a spell by Id, on the ground at the specified location. 
        ///   Returns RunStatus.Success if successful, RunStatus.Failure otherwise.
        /// </summary>
        public static Composite CastOnGround(int spellid, LocationRetriever onLocation)
        {
            return CastOnGround(spellid, onLocation, ret => true);
        }

        public static Composite CastOnGround(int spellid, LocationRetriever onLocation, CanRunDecoratorDelegate requirements, bool waitForSpell = false)
        {
            return
                new Decorator(
                    ret => onLocation != null && requirements(ret), //  && PRSettings.Instance.UseAoEAbilities
                    new Sequence(
                        new Action(ret => SpellManager.Cast(spellid)),

                        new DecoratorContinue(ctx => waitForSpell,
                            new WaitContinue(1,
                                ret =>
                                StyxWoW.Me.CurrentPendingCursorSpell != null &&
                                StyxWoW.Me.CurrentPendingCursorSpell.Id == spellid, new ActionAlwaysSucceed())),

                        new Action(ret => SpellManager.ClickRemoteLocation(onLocation(ret))),
                        new Action(ret => Log.Info(String.Format("[Casting: {0}] [Loc: {1}]", WoWSpell.FromId(spellid).Name, onLocation(ret))))
                        ));
        }

        public static Composite CastOnUnitLocation(string spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null)
        {
            return CastOnGround(spell, loc=> onUnit(loc).Location, req => reqs(req));
        }

        public static Composite CastOnUnitLocation(string spell, UnitSelectionDelegate onUnit, Selection<bool> reqs = null, bool waitForSpell = false)
        {
            return CastOnGround(spell, loc => onUnit(loc).Location, req => reqs(req), waitForSpell);
        }


        #endregion

        #region Spells - methods to handle Spells such as cooldowns

        public static TimeSpan GetSpellCooldown(string spell)
        {
            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.CooldownTimeLeft : results.Original.CooldownTimeLeft;
            }

            return TimeSpan.MaxValue;
        }

        public static TimeSpan GetSpellCooldown(int spell)
        {
            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.CooldownTimeLeft : results.Original.CooldownTimeLeft;
            }

            return TimeSpan.MaxValue;
        }

        public static bool SpellOnCooldown(string spell)
        {

            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.Cooldown : results.Original.Cooldown;
            }

            return false;
        }

        public static bool SpellOnCooldown(int spell)
        {

            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.Cooldown : results.Original.Cooldown;
            }

            return false;
        }

        public static double GetSpellCastTime(string spell)
        {
            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.CastTime / 1000.0 : results.Original.CastTime / 1000.0;
            }

            return 99999.9;
        }

        public static double GetSpellCastTime(int spell)
        {
            SpellFindResults results;
            if (SpellManager.FindSpell(spell, out results))
            {
                return results.Override != null ? results.Override.CastTime / 1000.0 : results.Original.CastTime / 1000.0;
            }

            return 99999.9;
        }

        #endregion

        #region Auras - methods to handle Auras/Buffs/Debuffs

        public static uint StackCount(WoWUnit unit, string aura)
        {
            {
                if (unit != null)
                {
                    WoWAura result = unit.GetAllAuras().FirstOrDefault(a => a.Name == aura && a.StackCount > 0);
                    if (result != null) return result.StackCount;
                }
                return 0;
            }
        }

        public static uint GetAuraStackCount(string aura)
        {
            var result = StyxWoW.Me.GetAuraByName(aura);
            if (result != null)
            {
                if (result.StackCount > 0)
                    return result.StackCount;
            }

            return 0;
        }

        public static uint GetAuraStackCount(int spellId)
        {
            var result = StyxWoW.Me.GetAuraById(spellId);
            if (result != null)
            {
                if (result.StackCount > 0)
                    return result.StackCount;
            }

            return 0;
        }

        public static uint GetAuraStackCount(int spellId, WoWUnit unit)
        {
            var result = unit.GetAuraById(spellId);
            if (result != null)
            {
                if (result.StackCount > 0)
                    return result.StackCount;
            }

            return 0;
        }

        public static double GetAuraTimeLeft(string aura)
        {
            return GetAuraTimeLeft(aura, StyxWoW.Me);
        }

        public static double GetAuraTimeLeft(string aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {
                var result = onUnit.GetAuraByName(aura);
                if (result != null)
                {
                    if (result.TimeLeft.TotalSeconds > 0)
                        return result.TimeLeft.TotalSeconds;
                }
            }
            return 0;
        }

        public static double GetAuraTimeLeftMs(string aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {
                var result = onUnit.GetAuraByName(aura);
                if (result != null)
                {
                    //if (result.TimeLeft.TotalMilliseconds > 0)
                        return result.TimeLeft.TotalMilliseconds;
                }
            }
            return 0;
        }

        public static double GetAuraTimeLeft(int aura)
        {
            return GetAuraTimeLeft(aura, StyxWoW.Me);
        }

        public static double GetAuraTimeLeft(int aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {
                var result = onUnit.GetAuraById(aura);
                if (result != null)
                {
                    if (result.TimeLeft.TotalSeconds > 0)
                        return result.TimeLeft.TotalSeconds;
                }
            }
            return 0;
        }

        public static double GetMyAuraTimeLeft(string aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {
                var result = onUnit.GetAllAuras().FirstOrDefault(a => a.Name == aura && a.CreatorGuid == Me.Guid);
                if (result != null && result.TimeLeft.TotalSeconds > 0)
                    return result.TimeLeft.TotalSeconds;
            }
            return 0;
        }

        public static double GetMyAuraTimeLeft(int aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {
                var result = onUnit.GetAllAuras().FirstOrDefault(a => a.SpellId == aura && a.CreatorGuid == Me.Guid);
                if (result != null && result.TimeLeft.TotalSeconds > 0)
                    return result.TimeLeft.TotalSeconds;
            }
            return 0;
        }

       

        /// <summary>
        /// 05-01-2013 By Mirabis
        /// </summary>
        /// <param name="aura"></param>
        /// <param name="onUnit"></param>
        /// <returns></returns>
        public static double GetMyAuraTimeLeft(string[] aura, WoWUnit onUnit)
        {
            if (onUnit != null)
            {

                 var auras = onUnit.GetAllAuras();
                 var hashes = new HashSet<string>(aura);
                var result = auras.FirstOrDefault(a => hashes.Contains(a.Name) && a.CreatorGuid == Me.Guid);
                  if (result != null && result.TimeLeft.TotalSeconds > 0)
                    return result.TimeLeft.TotalSeconds;
            }
            return 0;
        }

        public static bool HasMyAura(string aura, WoWUnit u)
        {
            return u.GetAllAuras().Any(a => a.Name == aura && a.CreatorGuid == Me.Guid);
        }

        public static bool AnyHasMyAura(string aura)
        {
            return Unit.AttackableUnits.Any(u => u.GetAllAuras().Any(a => a.Name == aura && a.CreatorGuid == Me.Guid));
        }

        public static bool AllHasMyAura(string aura)
        {
            return Unit.AttackableUnits.All(u => u.GetAllAuras().Any(a => a.Name == aura && a.CreatorGuid == Me.Guid));
        }

        #endregion

        #region HasAura - Internal Extenstions

        public static bool HasRaidDebuff(this WoWUnit unit)
        {
            if (unit == null) return false;

            return unit != null && unit.HasAnyAura(SpellLists.BuffRaidValid);
        }


        public static bool HasMyAura(this WoWUnit unit, string aura)
        {
            if (unit == null) return false;

            return unit.GetAllAuras().Any(a => a.Name == aura && a.CreatorGuid == Me.Guid);
        }

        public static bool HasMyAura(this WoWUnit unit, int spellId) 
        {
            if (unit == null) return false;

            return unit.GetAllAuras().Any(a => a.SpellId == spellId && a.CreatorGuid == Me.Guid);

        }

        public static bool HasAnyAura(this WoWUnit unit, params string[] auraNames)
        {
            if (unit == null) return false;

            var auras = unit.GetAllAuras();
            var hashes = new HashSet<string>(auraNames);
            return auras.Any(a => hashes.Contains(a.Name));
        }

        public static bool HasAnyAura(this WoWUnit unit, HashSet<int> auraIDs)
        {
            var auras = unit.GetAllAuras();
            return auras.Any(a => auraIDs.Contains(a.SpellId));
        }

        public static bool HasAnyOfMyAuras(this WoWUnit unit, HashSet<int> auraIDs)
        {
            if (unit == null) return false;

            var auras = unit.GetAllAuras();
            return auras.Any(a => auraIDs.Contains(a.SpellId) && a.CreatorGuid == Me.Guid);
        }

        public static bool HasAllAuras(this WoWUnit unit, params string[] auraNames)
        {
            if (unit == null) return false;

            return auraNames.All(unit.HasAura);
        }

        public static bool HasAllMyAuras(this WoWUnit unit, params string[] auraNames)
        {
            if (unit == null) return false;

            return auraNames.All(unit.HasMyAura);
        }

        public static bool HasAllAuras(this WoWUnit unit, params int[] auraIDs)
        {
            if (unit == null) return false;

            return auraIDs.All(unit.HasAura);
        }

        public static bool HasAnyAura(this WoWUnit unit, params int[] auraIDs)
        {
            if (unit == null) return false;

            return auraIDs.Any(unit.HasAura);
        }

        public static bool HasAuraWithMechanic(this WoWUnit unit, params WoWSpellMechanic[] mechanics)
        {
            if (unit == null) return false;

            var auras = unit.GetAllAuras();
            return auras.Any(a => mechanics.Contains(a.Spell.Mechanic));
        }

        public static bool HasAuraWithMechanic(this WoWUnit unit, params WoWApplyAuraType[] applyType)
        {
            if (unit == null) return false;

            var auras = unit.GetAllAuras();
            return auras.Any(a => a.Spell.SpellEffects.Any(se => applyType.Contains(se.AuraType)));
        }


        #endregion

        #region Moving - Internal Extentions

        /// <summary>
        /// Internal IsMoving check which ignores turning on the spot.
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        public static bool IsMoving(this WoWUnit unit)
        {
            return unit.MovementInfo.MovingBackward || unit.MovementInfo.MovingForward || unit.MovementInfo.MovingStrafeLeft || unit.MovementInfo.MovingStrafeRight;
        }

        /// <summary>
        /// Internal IsMoving check which ignores turning on the spot, and allows specifying how long you've been moving for before accepting it as actually moving. 
        /// </summary>
        /// <param name="unit"></param>
        /// <param name="movingDuration">Duration in MS how long the unit has been moving before accepting it as a moving unit</param>
        /// <returns></returns>
        public static bool IsMoving(this WoWUnit unit, int movingDuration)
        {
            return unit.IsMoving() && unit.MovementInfo.TimeMoved >= movingDuration;
        }

        #endregion

        #region Nested type: Selection

        internal delegate T Selection<out T>(object context);

        #endregion

        #region Extentions
        public static bool IsMelee(this WoWUnit unit)
        {
            {
                if (unit != null)
                {
                    switch (StyxWoW.Me.Class)
                    {
                        case WoWClass.Warrior:
                            return true;
                        case WoWClass.Paladin:
                            return StyxWoW.Me.Specialization != WoWSpec.PaladinHoly;
                        case WoWClass.Hunter:
                            return false;
                        case WoWClass.Rogue:
                            return true;
                        case WoWClass.Priest:
                            return false;
                        case WoWClass.DeathKnight:
                            return true;
                        case WoWClass.Shaman:
                            return StyxWoW.Me.Specialization == WoWSpec.ShamanEnhancement;
                        case WoWClass.Mage:
                            return false;
                        case WoWClass.Warlock:
                            return false;
                        case WoWClass.Druid:
                            return StyxWoW.Me.Specialization != WoWSpec.DruidRestoration &&
                                   StyxWoW.Me.Specialization != WoWSpec.DruidBalance;
                        case WoWClass.Monk:
                            return StyxWoW.Me.Specialization != WoWSpec.MonkMistweaver;
                    }
                }
                return false;
            }
        }
        #endregion

        #region GetTreePerformance

        // Usage: Spell.TreePerformance(true), // within a composite.
        private static readonly Stopwatch TreePerformanceTimer = new Stopwatch(); // lets see if we can get some performance on this one.

        internal static Composite TreePerformance(bool enable)
        {
            return new Action(ret =>
            {
                if (!enable)
                {
                    return RunStatus.Failure;
                }

                if (TreePerformanceTimer.ElapsedMilliseconds > 0)
                {
                    // NOTE: This dosnt account for Spell casts (meaning the total time is not the time to traverse the tree plus the current cast time of the spell)..this is actual time to traverse the tree.
                    Log.Info("[TreePerformance] Elapsed Time to traverse Tree: {0} ms", TreePerformanceTimer.ElapsedMilliseconds);
                    TreePerformanceTimer.Stop();
                    TreePerformanceTimer.Reset();
                }
                TreePerformanceTimer.Start();


                return RunStatus.Failure;
            });
        }

        

        #endregion


       
    }
}
